/**
 * Copyright (c) 2018-2023, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: cache for riscv64 generic platform
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2023-08-15     JasonHu           Init
 */

#include <base/cache.h>

#ifdef CONFIG_RISCV64_CACHE_C906

#define CACHE_LINE_BYTES (64)

/**
 * GCC version not support t-head cache flush, so we use fixed code to achieve.
 * The following function cannot be optimized.
 */
NX_PRIVATE void DcacheFlushRange(NX_UArch start, NX_UArch end) __attribute__((optimize("O0")));
NX_PRIVATE void DcacheInvalidateRange(NX_UArch start, NX_UArch end) __attribute__((optimize("O0")));
NX_PRIVATE void DcacheFlushInvalidateRange(NX_UArch start, NX_UArch end) __attribute__((optimize("O0")));
NX_PRIVATE void IcacheInvalidateRange(NX_UArch start, NX_UArch end) __attribute__((optimize("O0")));

NX_PRIVATE void DcacheFlushRange(NX_UArch start, NX_UArch end)
{
    NX_UArch i = start & ~(CACHE_LINE_BYTES - 1);

    for (; i < end; i += CACHE_LINE_BYTES)
    {
        /* asm volatile("dcache.cva %0\n"::"r"(i):"memory"); */
        /*
         * compiler always use a5 = i.
         * a6 not used, so we use a6 here.
         */
        asm volatile("mv a6, %0\n"::"r"(i):"memory");   /* a6 = a5(i) */
        asm volatile(".long 0x0257800b");               /* dcache.cva a6 */
    }
    asm volatile(".long 0x01b0000b");   /* sync.is */
}

NX_PRIVATE void DcacheInvalidateRange(NX_UArch start, NX_UArch end)
{
    NX_UArch i = start & ~(CACHE_LINE_BYTES - 1);

    for (; i < end; i += CACHE_LINE_BYTES)
    {
        /* asm volatile("dcache.iva %0\n"::"r"(i):"memory"); */
        asm volatile("mv a6, %0\n"::"r"(i):"memory");   /* a6 = a5(i) */
        asm volatile(".long 0x0268000b");               /* dcache.iva a6 */
    }
    asm volatile(".long 0x01b0000b");
}

NX_PRIVATE void DcacheFlushInvalidateRange(NX_UArch start, NX_UArch end)
{
    NX_UArch i = start & ~(CACHE_LINE_BYTES - 1);

    for (; i < end; i += CACHE_LINE_BYTES)
    {
        /* asm volatile("dcache.civa %0\n"::"r"(i):"memory"); */
        asm volatile("mv a6, %0\n"::"r"(i):"memory");   /* a6 = a5(i) */
        asm volatile(".long 0x0278000b");               /* dcache.civa a6 */
    }
    asm volatile(".long 0x01b0000b");
}

NX_PRIVATE void IcacheInvalidateRange(NX_UArch start, NX_UArch end)
{
    NX_UArch i = start & ~(CACHE_LINE_BYTES - 1);

    for (; i < end; i += CACHE_LINE_BYTES)
    {
        /* asm volatile("icache.iva %0\n"::"r"(i):"memory"); */
        asm volatile("mv a6, %0\n"::"r"(i):"memory");   /* a6 = a5(i) */
        asm volatile(".long 0x0308000b");               /* icache.iva a6 */
    }
    asm volatile(".long 0x01b0000b");
}

NX_PRIVATE NX_U32 RV_DcacheStatus(void)
{
    return NX_CACHE_ENABLED;
}

NX_PRIVATE NX_U32 RV_IcacheStatus(void)
{
    return NX_CACHE_ENABLED;
}

NX_PRIVATE NX_U32 RV_DcacheLineSize(void)
{
    return CACHE_LINE_BYTES;
}

NX_PRIVATE NX_U32 RV_IcacheLineSize(void)
{
    return CACHE_LINE_BYTES;
}

NX_PRIVATE void RV_IcacheInvalidateAll(void)
{
    /* asm volatile("icache.iall\n":::"memory"); */
    asm volatile(".long 0x0100000b\n":::"memory");
}

NX_PRIVATE void RV_IcacheInvalidateLine(void *addr)
{
    NX_Addr alignAddr;
    alignAddr = NX_ALIGN_DOWN((NX_UArch)addr, CACHE_LINE_BYTES);
    IcacheInvalidateRange(alignAddr, alignAddr + CACHE_LINE_BYTES);
}

NX_PRIVATE void RV_IcacheInvalidateRange(void *addr, NX_Size len)
{
    IcacheInvalidateRange((NX_UArch)addr, (NX_UArch)((NX_U8 *)addr + len));
}

NX_PRIVATE void RV_DcacheInvalidateAll(void)
{
    /* asm volatile("dcache.iall\n":::"memory"); */
    asm volatile(".long 0x0020000b\n":::"memory");
}

NX_PRIVATE void RV_DcacheInvalidateLine(void *addr)
{
    NX_Addr alignAddr;
    alignAddr = NX_ALIGN_DOWN((NX_UArch)addr, CACHE_LINE_BYTES);
    DcacheInvalidateRange(alignAddr, alignAddr + CACHE_LINE_BYTES);
}

NX_PRIVATE void RV_DcacheInvalidateRange(void *addr, NX_Size len)
{
    DcacheInvalidateRange((NX_UArch)addr, (NX_UArch)((NX_U8 *)addr + len));
}

NX_PRIVATE void RV_DcacheFlushAll(void)
{
    /* asm volatile("dcache.call\n":::"memory"); */
    asm volatile(".long 0x0010000b\n":::"memory");
}

NX_PRIVATE void RV_DcacheFlushLine(void *addr)
{
    NX_Addr alignAddr;
    alignAddr = NX_ALIGN_DOWN((NX_UArch)addr, CACHE_LINE_BYTES);
    DcacheFlushRange(alignAddr, alignAddr + CACHE_LINE_BYTES);
}

NX_PRIVATE void RV_DcacheFlushRange(void *addr, NX_Size len)
{
    DcacheFlushRange((NX_UArch)addr, (NX_UArch)((NX_U8 *)addr + len));
}

NX_PRIVATE void RV_DcacheClearAll(void)
{
    /* asm volatile("dcache.ciall\n":::"memory"); */
    asm volatile(".long 0x0030000b\n":::"memory");
}

NX_PRIVATE void RV_DcacheClearLine(void *addr)
{
    NX_Addr alignAddr;
    alignAddr = NX_ALIGN_DOWN((NX_UArch)addr, CACHE_LINE_BYTES);
    DcacheFlushInvalidateRange(alignAddr, alignAddr + CACHE_LINE_BYTES);
}

NX_PRIVATE void RV_DcacheClearRange(void *addr, NX_Size len)
{
    DcacheFlushInvalidateRange((NX_UArch)addr, (NX_UArch)((NX_U8 *)addr + len));
}

NX_PRIVATE void RV_DcacheFlushIcacheInvalidate(void *addr, NX_Size len)
{
    RV_DcacheFlushRange(addr, len);
    RV_IcacheInvalidateRange(addr, len);
}

NX_INTERFACE struct NX_CacheOps NX_CacheOpsInterface = 
{
    .dcacheEnable                   = NX_NULL,
    .dcacheDisable                  = NX_NULL,
    .icacheEnable                   = NX_NULL,
    .icacheDisable                  = NX_NULL,
    .dcacheStatus                   = RV_DcacheStatus,
    .icacheStatus                   = RV_IcacheStatus,
    .dcacheLineSize                 = RV_DcacheLineSize,
    .icacheLineSize                 = RV_IcacheLineSize,
    .icacheInvalidateAll            = RV_IcacheInvalidateAll,
    .icacheInvalidateLine           = RV_IcacheInvalidateLine,
    .icacheInvalidateRange          = RV_IcacheInvalidateRange,
    .dcacheInvalidateAll            = RV_DcacheInvalidateAll,
    .dcacheInvalidateLine           = RV_DcacheInvalidateLine,
    .dcacheInvalidateRange          = RV_DcacheInvalidateRange,
    .dcacheFlushAll                 = RV_DcacheFlushAll,
    .dcacheFlushLine                = RV_DcacheFlushLine,
    .dcacheFlushRange               = RV_DcacheFlushRange,
    .dcacheClearAll                 = RV_DcacheClearAll,
    .dcacheClearLine                = RV_DcacheClearLine,
    .dcacheClearRange               = RV_DcacheClearRange,
    .dcacheFlushIcacheInvalidate    = RV_DcacheFlushIcacheInvalidate,
};
#endif